﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace JellystonPark_WF.Model
{
    public class JellystoneModel
    {
        public enum GameDifficulty { Easy, Moderate, Hard }

        #region Fields

        private int _basketsAll;
        private int _basketsFounded;
        private int _steps;
        private int _currentLevel;
        private int _gameTime;
        private bool _isLevelOver;
        private bool _isLevelWon;
        private GameDifficulty _diff;
        private JellystonePlayer _player;
        private List<JellystoneTable> _initialTables;
        private List<JellystoneTable> _tables;
        private Timer _gameTimer;
        private Timer _keeperTimer;

        #endregion

        #region Properties

        /// <summary>
        /// Játéktábla lekérdezése.
        /// </summary>
        public List<JellystoneTable> Tables { get { return _tables; } }
        /// <summary>
        /// Megtett lépések számának lekérdezése.
        /// </summary>
        public int Steps { get { return _steps; } }
        /// <summary>
        /// A begyűjtendő kosarak száma.
        /// </summary>
        public int BasketsAll { get { return _basketsAll; } }
        /// <summary>
        /// A már begyűjtött kosarak száma.
        /// </summary>
        public int BasketsFounded { get { return _basketsFounded; } }
        /// <summary>
        /// Aktuális pálya.
        /// </summary>
        public int CurrentLevel { get { return _currentLevel; } }
        /// <summary>
        /// Játékos lekérdezése és beállítása. Ha új játékost állítunk be, újrakezdődik a játék.
        /// </summary>
        public JellystonePlayer Player { get { return _player; } set { _player = value; RestartGame(); } }
        /// <summary>
        /// Nehézségi szint lekérdezése, beállítása.
        /// </summary>
        public GameDifficulty Difficulty { get { return _diff; } set { _diff = value; } }
        /// <summary>
        /// Akutális pályához tartozó tábla lekérdezése.
        /// </summary>
        public JellystoneTable CurrentTable { get { return _tables[CurrentLevel]; } set { _tables[CurrentLevel] = value; } }
        /// <summary>
        /// Játék szüneteltetésének lekérdezése.
        /// </summary>
        public bool Paused { get { return !_gameTimer.Enabled; } }
        /// <summary>
        /// Játékkal eltöltött idő lekérdezése.
        /// </summary>
        public int GameTime { get { return _gameTime; } }
        /// <summary>
        /// Játék végének ellenőrzése.
        /// </summary>
        public bool IsOver { get { return _isLevelOver; } }

        #endregion

        #region Events

        public event EventHandler<JellystoneResultEventArgs> Initialized;
        /// <summary>
        /// Játék végének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> LevelOver;
        /// <summary>
        /// Játékidő léptetésének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> Advanced;
        /// <summary>
        /// Maci Laci lépésének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> Stepped;
        /// <summary>
        /// Vadőrök mozgatásának eseménye.
        /// </summary>
        public event EventHandler<JellystoneMovementEventArgs> FigureMoved;
        /// <summary>
        /// Kosár találás eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> GotBasket;
        /// <summary>
        /// Játék végének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> GameOver;

        #endregion

        #region Constructor

        /// <summary>
        /// A Jellystone játék példányosítása.
        /// </summary>
        public JellystoneModel(string playerName, JellystoneModel.GameDifficulty diff)
        {
            _player = new JellystonePlayer(playerName);
            _diff = diff;

            _gameTimer = new Timer();
            _gameTimer.Interval = 1000;
            _gameTimer.Elapsed += new ElapsedEventHandler(Elapsed);

            _keeperTimer = new Timer();
            switch (diff)
            {
                case GameDifficulty.Easy:
                    _keeperTimer.Interval = 1500;
                    break;
                case GameDifficulty.Moderate:
                    _keeperTimer.Interval = 1000;
                    break;
                default:
                    _keeperTimer.Interval = 500;
                    break;
            }
            _keeperTimer.Elapsed += new ElapsedEventHandler(MoveKeepers);
        }

        #endregion

        #region Public Methods

        public void Init()
        {
            _steps = 0;
            _gameTime = 0;
            _basketsFounded = 0;
            _isLevelOver = false;
            _isLevelWon = false;
            _basketsAll = _tables[_currentLevel].CountBaskets;
            OnInitialized();
        }

        /// <summary>
        /// Játék elindítása paraméterként kapott pályával.
        /// </summary>
        public void NewGame(List<JellystoneTable> tables)
        {
            _currentLevel = 0;
            _tables = new List<JellystoneTable>(tables);
            _initialTables = new List<JellystoneTable>(tables.Count); //TODO: TÉNYLEG read only lista impl.
            tables.ForEach((item) =>
            {
                _initialTables.Add(new JellystoneTable(item));
            });
            Init();
        }

        public void StartLevel()
        {
            if (_gameTime != 0)
                return;
            _gameTimer.Start();
            _keeperTimer.Start();
        }

        public void RestartGame()
        {
            _currentLevel = 0;
            _player.GameTime = 0;
            _player.Steps = 0;
            _tables.Clear();
            _tables = new List<JellystoneTable>(_initialTables.Count);
            _initialTables.ForEach((item) =>
            {
                _tables.Add(new JellystoneTable(item));
            });
            Init();
            StartLevel();
        }

        public void RestartLevel()
        {
            CurrentTable = new JellystoneTable(_initialTables[CurrentLevel]);
            Init();
            StartLevel();
        }

        public void NextLevel()
        {
            if (_currentLevel + 1 < _tables.Count)
            {
                ++_currentLevel;
                Init();
            }
            else if (GameOver != null)
                GameOver(this, new JellystoneResultEventArgs(_player.GameTime, _player.Steps, 0, 0, true));
        }
        /// <summary>
        /// Játék szüneteltetése
        /// </summary>
        public void Pause()
        {
            if (Paused)
                return;

            _gameTimer.Stop();
            _keeperTimer.Stop();
        }
        /// <summary>
        /// Játék folytatása
        /// </summary>
        public void Continue()
        {
            if (!Paused || IsOver)
                return;

            _gameTimer.Start();
            _keeperTimer.Start();
        }
        /// <summary>
        /// 
        /// </summary>
        public void Toggle()
        {
            if (!Paused)
                Pause();
            else
                Continue();
        }

        public void StepUp()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2 - 1);
        }

        public void StepRight()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1 + 1, CurrentTable.YogyCurrentPos.Item2);
        }

        public void StepDown()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2 + 1);
        }

        public void StepLeft()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1 - 1, CurrentTable.YogyCurrentPos.Item2);
        }

        #endregion

        #region Private event handler

        private void Elapsed(object sender, ElapsedEventArgs e)
        {
            OnAdvance();
        }

        /// <summary>
        /// Vadőrök mozgatása
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MoveKeepers(object sender, EventArgs e)
        {
            List<Tuple<int, int>> movedKeepers = new List<Tuple<int, int>>();
            for (int i = 0; i < CurrentTable.Count; ++i)
                for (int j = 0; j < CurrentTable.Count; ++j)
                    if (!movedKeepers.Contains(Tuple.Create<int, int>(i, j))) //A már mozgatott vadőröket kihagyjuk
                        switch (CurrentTable[i, j])
                        {
                            case JellystoneTable.Field.RangerHL:
                                if (!CurrentTable.SetValue(i - 1, j, JellystoneTable.Field.RangerHL))
                                {
                                    if (CurrentTable.SetValue(i + 1, j, JellystoneTable.Field.RangerHR))
                                    {
                                        CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                        OnFigureMoved(i, j, i + 1, j);
                                        movedKeepers.Add(Tuple.Create<int, int>(i + 1, j));
                                        if (IsInTheNeighborhood(i + 1, j, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                    OnFigureMoved(i, j, i - 1, j);
                                    movedKeepers.Add(Tuple.Create<int, int>(i - 1, j));
                                    if (IsInTheNeighborhood(i - 1, j, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.Field.RangerHR:
                                if (!CurrentTable.SetValue(i + 1, j, JellystoneTable.Field.RangerHR))
                                {
                                    if (CurrentTable.SetValue(i - 1, j, JellystoneTable.Field.RangerHL))
                                    {
                                        CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                        OnFigureMoved(i, j, i - 1, j);
                                        movedKeepers.Add(Tuple.Create<int, int>(i - 1, j));
                                        if (IsInTheNeighborhood(i - 1, j, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                    OnFigureMoved(i, j, i + 1, j);
                                    movedKeepers.Add(Tuple.Create<int, int>(i + 1, j));
                                    if (IsInTheNeighborhood(i + 1, j, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.Field.RangerVD:
                                if (!CurrentTable.SetValue(i, j + 1, JellystoneTable.Field.RangerVD))
                                {
                                    if (CurrentTable.SetValue(i, j - 1, JellystoneTable.Field.RangerVU))
                                    {
                                        CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                        OnFigureMoved(i, j, i, j - 1);
                                        movedKeepers.Add(Tuple.Create<int, int>(i, j - 1));
                                        if (IsInTheNeighborhood(i, j - 1, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                    OnFigureMoved(i, j, i, j + 1);
                                    movedKeepers.Add(Tuple.Create<int, int>(i, j + 1));
                                    if (IsInTheNeighborhood(i, j + 1, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.Field.RangerVU:
                                if (!CurrentTable.SetValue(i, j - 1, JellystoneTable.Field.RangerVU))
                                {
                                    if (CurrentTable.SetValue(i, j + 1, JellystoneTable.Field.RangerVD))
                                    {
                                        CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                        OnFigureMoved(i, j, i, j + 1);
                                        movedKeepers.Add(Tuple.Create<int, int>(i, j + 1));
                                        if (IsInTheNeighborhood(i, j + 1, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable.SetValue(i, j, JellystoneTable.Field.Blank);
                                    OnFigureMoved(i, j, i, j - 1);
                                    movedKeepers.Add(Tuple.Create<int, int>(i, j - 1));
                                    if (IsInTheNeighborhood(i, j - 1, new JellystoneTable.Field[] { JellystoneTable.Field.Yogi }))
                                        OnLevelOver();
                                }
                                break;
                        }
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Megpróbál Maci Lacival a paraméterként megadott koordinátákra lépni.
        /// </summary>
        /// <returns>Ha sikerült a megadott koordinátákra lépni igaz, különben hamis.</returns>
        private void StepTo(int i, int j)
        {
            if (Paused)
                return;
            Tuple<int, int> currPos = Tuple.Create<int, int>(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2);
            JellystoneTable.Field toField = CurrentTable[i, j];
            if (CurrentTable.SetValue(i, j, JellystoneTable.Field.Yogi))
            {
                CurrentTable.SetValue(currPos.Item1, currPos.Item2, JellystoneTable.Field.Blank);
                
                // Ha Maci Laci öngyilkos akar lenni
                if (IsInTheNeighborhood(i, j, new JellystoneTable.Field[]{ JellystoneTable.Field.RangerHL,
                                                                           JellystoneTable.Field.RangerHR,
                                                                           JellystoneTable.Field.RangerVD,
                                                                           JellystoneTable.Field.RangerVU
                                                                          }
                                       )
                    )
                {
                    OnFigureMoved(currPos.Item1, currPos.Item2, i, j);
                    OnStep();
                    OnLevelOver();
                    return;
                }
                OnFigureMoved(currPos.Item1, currPos.Item2, i, j);
                OnStep();
                if (JellystoneTable.Field.Basket == toField)
                    OnGotBasket();
            }
        }

        /// <summary>
        /// Adott hely szomszédságában van-e f értékű mező.
        /// </summary>
        /// <param name="i">Oszlopindex</param>
        /// <param name="j">Sorindex</param>
        /// <returns>Ha van igaz, különben hamis.</returns>
        private bool IsInTheNeighborhood(int i, int j, IEnumerable<JellystoneTable.Field> f)
        {
            if (f.Contains(CurrentTable[i - 1, j]) ||
                f.Contains(CurrentTable[i - 1, j - 1]) ||
                f.Contains(CurrentTable[i - 1, j + 1]) ||
                f.Contains(CurrentTable[i, j - 1]) ||
                f.Contains(CurrentTable[i, j + 1]) ||
                f.Contains(CurrentTable[i + 1, j - 1]) ||
                f.Contains(CurrentTable[i + 1, j]) ||
                f.Contains(CurrentTable[i + 1, j + 1])
                )
                return true;
            return false;
        }

        #endregion

        #region Private event methods

        private void OnInitialized()
        {
            if (null != Initialized)
                Initialized(this, new JellystoneResultEventArgs(_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
        }

        private void OnAdvance()
        {
            if (null != Advanced)
                Advanced(this, new JellystoneResultEventArgs(++_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
        }

        private void OnStep()
        {
            if (null != Stepped)
            {
                Stepped(this, new JellystoneResultEventArgs(_gameTime, ++_steps, _basketsFounded, _basketsAll, _isLevelWon));
            }
        }

        private void OnFigureMoved(int xf, int yf, int xt, int yt)
        {
            if (null != FigureMoved)
                FigureMoved(this, new JellystoneMovementEventArgs(xf, yf, xt, yt));
        }

        private void OnGotBasket()
        {
            if (null != GotBasket)
                GotBasket(this, new JellystoneResultEventArgs(_gameTime, _steps, ++_basketsFounded, _basketsAll, _isLevelWon));
            if (_basketsAll == _basketsFounded) //Nyertünk-e
            {
                _isLevelWon = true;
                OnLevelOver();
            }
        }

        private void OnLevelOver()
        {
            if (null != LevelOver)
            {
                _isLevelOver = true;
                _player.GameTime += _gameTime;
                _player.Steps += _steps;
                Pause();
                LevelOver(this, new JellystoneResultEventArgs(_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
            }
        }
        #endregion
    }
}
